package org.spring.blog.service.impl;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.vladsch.flexmark.ext.jekyll.tag.JekyllTagExtension;
import com.vladsch.flexmark.ext.tables.TablesExtension;
import com.vladsch.flexmark.ext.toc.SimTocExtension;
import com.vladsch.flexmark.ext.toc.TocExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.data.MutableDataSet;
import org.spring.blog.dao.ArticleDao;
import org.spring.blog.dao.ArticleNoContentDao;
import org.spring.blog.dao.CommentDao;
import org.spring.blog.dao.LabelDao;
import org.spring.blog.pojo.*;
import org.spring.blog.response.ResponseResult;
import org.spring.blog.response.ResponseState;
import org.spring.blog.service.IArticleService;
import org.spring.blog.service.ISolrService;
import org.spring.blog.service.IUserService;
import org.spring.blog.utils.LRConstants;
import org.spring.blog.utils.RedisUtil;
import org.spring.blog.utils.SnowflakeIdWorker;
import org.spring.blog.utils.TextUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.transaction.Transactional;
import java.util.*;

@Transactional
@Service
public class ArticleService implements IArticleService {

    @Autowired
    CommentDao commentDao;

    @Autowired
    LabelDao labelDao;

    @Autowired
    ISolrService solrService;

    @Autowired
    ArticleDao articleDao;

    @Autowired
    ArticleNoContentDao articleNoContentDao;

    @Autowired
    UserService userService;

    @Autowired
    SnowflakeIdWorker snowflakeIdWorker;

    @Autowired
    RedisUtil redisUtil;

    @Autowired
    private Gson gson;

    @Autowired
    Random random;

    /**
     * 置顶文章
     * 为置顶就置顶
     * 置顶了就取消置顶
     *
     * @param _id@return 自定义返回对象
     */
    @Override
    public ResponseResult topArticle(String _id) {
        // check env
        // 判断是否可以置顶，是否已经删除了
        Article oneById = articleDao.findOneById(_id);

        if (oneById == null) {
            return ResponseResult.failed(ResponseState.data_not_have_message, ResponseState.data_not_have_code);
        }

        // complete data
        switch (oneById.getState()) {
            case LRConstants.db_publish_status:
                oneById.setState(IArticleService.constant.db_top_state_article);
                break;
            case IArticleService.constant.db_top_state_article:
                oneById.setState(LRConstants.db_publish_status);
                break;
            case IArticleService.constant.db_draft_state_article:
            case LRConstants.db_ban_status:
            default:
                return ResponseResult.failed(ResponseState.not_support_operation_message, ResponseState.not_support_operation_code);
        }

        // change data
        articleDao.save(oneById);

        // change cache
        redisUtil.del(constant.redis_list_article_first_page_cache);

        return ResponseResult.success();
    }

    /**
     * 获取相关推荐文章的接口
     * 通过文章中的 标签 来给用户推荐文章
     * 相关的文章但不要一样， 安卓不要在推荐安卓
     * 可以推荐一下 linux 这样相关的 操作系统
     * <p>
     * 如果没有相关文章 则获取最新的文章
     *
     * @param _articleId 文章id
     * @param _size      数量
     * @return 自定义返回对象
     */
    @Override
    public ResponseResult listRecommendArticle(String _articleId, int _size) {
        // 获取文章的标签
        String labels = articleDao.queryLabelsById(_articleId);

        List<String> labelList = new ArrayList<>();

        if (labels.contains("-")) {
            String[] split = labels.split("-");

            List<String> strings = Arrays.asList(split);

            labelList.addAll(strings);
        } else {
            labelList.add(labels);
        }

        String label = labelList.get(random.nextInt(labelList.size()));
        // 根据一个标签获取推荐文章
        List<ArticleNoContent> articleByLikeLabel = articleNoContentDao.listArticleByLikeLabel(label, _size, _articleId, LRConstants.db_publish_status, IArticleService.constant.db_top_state_article);

        // 相关推荐的数量不够，找最新的文章 代替
        if (articleByLikeLabel.size() < _size) {
            List<ArticleNoContent> newArticle = articleNoContentDao.listLastedArticleBySize(_articleId, _size - articleByLikeLabel.size(), LRConstants.db_publish_status, IArticleService.constant.db_top_state_article);

            articleByLikeLabel.addAll(newArticle);
        }
        return ResponseResult.success().setData(articleByLikeLabel);
    }

    /**
     * 根据标签获取文章列表
     *
     * @param _page  页码
     * @param _size  页面大小
     * @param _label 标签
     * @return 自定义返回对象
     */
    @Override
    public ResponseResult listArticleByLabel(int _page, int _size, String _label) {
        if (_page < IArticleService.constant.list_default_page_num) {
            _page = IArticleService.constant.list_default_page_num;
        }

        if (_size < constant.list_min_page_size) {
            _size = IArticleService.constant.list_min_page_size;
        }

        // complete data from mysql database
        Sort sort = new Sort(Sort.Direction.DESC, "createTime");

        Pageable pageable = PageRequest.of(_page - 1, _size, sort);

        Page<ArticleNoContent> all = articleNoContentDao.findAll(new Specification<ArticleNoContent>() {
            private static final long serialVersionUID = 2933030017941669143L;

            @Override
            public Predicate toPredicate(Root<ArticleNoContent> _root, CriteriaQuery<?> _criteriaQuery, CriteriaBuilder _criteriaBuilder) {
                // 单独的限制条件
                Predicate labels = _criteriaBuilder.like(_root.get("labels").as(String.class), "%" + _label + "%");
                Predicate state = _criteriaBuilder.equal(_root.get("state").as(String.class), LRConstants.db_publish_status);
                Predicate topState = _criteriaBuilder.equal(_root.get("state").as(String.class), IArticleService.constant.db_top_state_article);

                // 限制条件之间的关系
                // 状态满足一个就够了
                // ( || = or)
                Predicate orState = _criteriaBuilder.or(state, topState);

                // ( && = and)
                return _criteriaBuilder.and(orState, labels);
            }
        }, pageable);

        return ResponseResult.success().setData(all);
    }

    /**
     * 查询标签云
     *
     * @param _size 数量
     * @return 自定义返回对象
     */
    @Override
    public ResponseResult listLabels(int _size) {
        if (_size < IArticleService.constant.list_min_page_size) {
            _size = IArticleService.constant.list_min_page_size;
        }

        Sort sort = new Sort(Sort.Direction.DESC, "count");

        Pageable pageable = PageRequest.of(0, _size, sort);

        Page<Label> all = labelDao.findAll(pageable);

        return ResponseResult.success().setData(all);
    }

    /**
     * 获取文章数量
     *
     * @return 自定义返回对象
     */
    @Override
    public ResponseResult getArticleCount() {
        long count = articleDao.count();
        return ResponseResult.success().setData(count);
    }

    /**
     * 查询文章
     * 需要返回数据库的文章内容
     *
     * @param _id 文章id
     * @return 自定义返回对象
     */
    @Override
    public ResponseResult queryByIdForAdmin(String _id) {
        // check data
        User user = userService.checkUserIsLogin();

        if (user == null) {
            return ResponseResult.failed(ResponseState.user_is_login_message, ResponseState.user_is_login_code);
        }
        Article article = articleDao.findOneById(_id);

        if (!article.getUserId().equals(user.getId())) {
            return ResponseResult.failed(ResponseState.no_power_message, ResponseState.no_power_code);
        }

        // return data
        return ResponseResult.success().setData(article);
    }

    /**
     * 增加文章
     *
     * @param _article 文章对象
     * @return 自定义返回对象
     */
    @Override
    public ResponseResult insert(Article _article) {

        switch (_article.getState()) {
            case LRConstants.db_publish_status:
                break;
            case IArticleService.constant.db_draft_state_article:
                // check data
                if (TextUtil.isEmpty(_article.getTitle()) ||
                        TextUtil.isEmpty(_article.getType()) ||
                        TextUtil.isEmpty(_article.getSummary()) ||
                        TextUtil.isEmpty(_article.getContent()) ||
                        TextUtil.isEmpty(_article.getLabel())) {
                    return ResponseResult.failed(ResponseState.input_data_not_complete_message, ResponseState.input_data_not_complete_code);
                }

                if (_article.getTitle().length() > IArticleService.constant.title_max_length) {
                    return ResponseResult.failed(ResponseState.article_title_length_too_long_message, ResponseState.article_title_length_too_long_code);
                }

                if (_article.getSummary().length() > IArticleService.constant.summary_max_length) {
                    return ResponseResult.failed(ResponseState.article_summary_length_too_long_message, ResponseState.article_summary_length_too_long_code);
                }
                break;

            default:
                throw new IllegalStateException("Unexpected value: " + _article.getState());
        }
        // check env
        User userIsLogin = userService.checkUserIsLogin();

        if (userIsLogin == null) {
            return ResponseResult.failed(ResponseState.user_not_login_message, ResponseState.user_not_login_code);
        }


        // complete data
        if (TextUtil.isEmpty(_article.getId())) {
            _article.setId(String.valueOf(snowflakeIdWorker.nextId()))
                    .setCreateTime(new Date())
                    .setUpdateTime(new Date())
                    .setUserId(userIsLogin.getId())
                    .setUserName(userIsLogin.getUserName());
        } else {
            // 重复提交内容
            // 防止重复 更新内容
            Article oneById = articleDao.findOneById(_article.getId());

            switch (oneById.getState()) {
                case IArticleService.constant.db_draft_state_article:
                case LRConstants.db_publish_status:
                    return ResponseResult.failed();
            }
        }

        _article.setUpdateTime(new Date());

        // insert data
        articleDao.save(_article);
        if (_article.getState().equals(LRConstants.db_publish_status)) {
            solrService.insertArticle(_article);
        }

        // 添加文章的标签到数据库
        setupLabels(_article.getLabel());

        // change cache
        redisUtil.del(constant.redis_list_article_first_page_cache);

        return ResponseResult.success();
    }

    /**
     * 添加文章的标签到数据库
     *
     * @param _labels 新的标签
     */
    private void setupLabels(String _labels) {

        // complete data
        Collection<String> labelList = new ArrayList<>();

        if (_labels.contains("-")) {
            String[] split = _labels.split("-");

            List<String> strings = Arrays.asList(split);

            labelList.addAll(strings);
        } else {
            labelList.add(_labels);
        }


        Label tempLabel = new Label();
        Date date = new Date();

        for (String label : labelList) {

            int i = labelDao.updateCountByName(label);

            if (i == 0) {
                tempLabel.setId(String.valueOf(snowflakeIdWorker.nextId()))
                        .setCount(1)
                        .setCreateTime(date)
                        .setName(label)
                        .setUpdateTime(date);

                labelDao.save(tempLabel);
            }
        }
    }

    /**
     * 列表获取文章
     *
     * @param _page       页码
     * @param _size       页面大小
     * @param _keyword    关键词
     * @param _categoryId 分类id
     * @param _state      删除 | 草稿 | 正常发布 | 置顶
     * @return 自定义返回对象
     */
    @Override
    public ResponseResult list(int _page, int _size, String _keyword, String _categoryId, String _state) {
        // check data
        if (_page < IArticleService.constant.list_default_page_num) {
            _page = IArticleService.constant.list_default_page_num;
        }

        boolean isSearch = TextUtil.notEmpty(_keyword) || TextUtil.notEmpty(_categoryId) || TextUtil.notEmpty(_state);

        if (_size < IArticleService.constant.list_min_page_size) {
            _size = IArticleService.constant.list_min_page_size;
        }

        if (_page == 1 && !isSearch) {
            String articleJson = (String) redisUtil.get(constant.redis_list_article_first_page_cache);

            if (!TextUtil.isEmpty(articleJson)) {
                LRPageList<ArticleNoContent> articleNoContentList = gson.fromJson(articleJson, new TypeToken<LRPageList<ArticleNoContent>>() {
                }.getType());

                return ResponseResult.success().setData(articleNoContentList);
            }
        }

        // complete data from mysql database
        Sort sort = new Sort(Sort.Direction.DESC, "createTime");

        Pageable pageable = PageRequest.of(_page - 1, _size, sort);

        Page<ArticleNoContent> all = articleNoContentDao.findAll(new Specification<ArticleNoContent>() {
            private static final long serialVersionUID = -7933500038804990073L;

            @Override
            public Predicate toPredicate(Root<ArticleNoContent> _root, CriteriaQuery<?> _criteriaQuery, CriteriaBuilder _criteriaBuilder) {

                Collection<Predicate> predicates = new ArrayList<>();

                if (!TextUtil.isEmpty(_state)) {
                    Predicate statePre = _criteriaBuilder.equal(_root.get("state").as(String.class), _state);

                    predicates.add(statePre);
                }

                if (!TextUtil.isEmpty(_categoryId)) {
                    Predicate categoryIdPre = _criteriaBuilder.equal(_root.get("categoryId").as(String.class), _categoryId);

                    predicates.add(categoryIdPre);
                }

                if (!TextUtil.isEmpty(_keyword)) {
                    Predicate titlePre = _criteriaBuilder.like(_root.get("title").as(String.class), "%" + _keyword + "%");

                    predicates.add(titlePre);
                }

                Predicate[] predicateAry = new Predicate[predicates.size()];

                predicates.toArray(predicateAry);

                return _criteriaBuilder.and(predicateAry);
            }
        }, pageable);

        LRPageList<ArticleNoContent> result = new LRPageList<>(all.getContent(), _page, all.getTotalElements(), all.getSize());

        // change cache
        if (_page == 1 && isSearch == false) {
            redisUtil.set(constant.redis_list_article_first_page_cache, gson.toJson(result), LRConstants.TimeValue.min_s * 15);
        }

        return ResponseResult.success().setData(result);
    }

    /**
     * 查询文章
     *
     * @param _id 文章id
     * @return 自定义返回对象
     */
    @Override
    public ResponseResult queryById(String _id) {

        // check data
        // 先从redis找，在到mysql找
        String articleJson = (String) redisUtil.get(constant.redis_article_cache_key + _id);

        if (!TextUtil.isEmpty(articleJson)) {
            Article article = gson.fromJson(articleJson, Article.class);
            // complete return data
            // 增加阅读数量统计
            long incrViewCount = redisUtil.incr(constant.redis_article_view_count_cache_key, 1);
            article.setViewCount(incrViewCount);
            // return data
            return ResponseResult.success().setData(article);
        }
        Article oneById = articleDao.findOneById(_id);

        if (oneById == null) {
            return ResponseResult.failed(ResponseState.data_not_have_message, ResponseState.data_not_have_code);
        }

        String state = oneById.getState();

        switch (state) {
            // 所有人都可以看的文章
            case LRConstants.db_publish_status:
            case IArticleService.constant.db_top_state_article:

                // change data
                // change article's view count
                Object articleViewCount = redisUtil.get(constant.redis_article_view_count_cache_key + _id);

                if (articleViewCount == null) {
                    redisUtil.set(constant.redis_article_view_count_cache_key + _id, String.valueOf(oneById.getViewCount()));
                }

                long incr = redisUtil.incr(constant.redis_article_view_count_cache_key, 1);

                oneById.setViewCount(incr);


                // oneById 被其他方法改变了 会影响数据库的内容
                solrService.updateArticle(oneById);
                // 数据库的操作靠着 return 最好，确认数据不会再变
                articleDao.save(oneById);

                // complete return data
                String htmlContent;
                if (IArticleService.constant.db_type_markdown.equals(oneById.getType())) {
                    MutableDataSet options = new MutableDataSet().set(Parser.EXTENSIONS, Arrays.asList(
                            TablesExtension.create(),
                            JekyllTagExtension.create(),
                            TocExtension.create(),
                            SimTocExtension.create()
                    ));

                    Parser parser = Parser.builder(options).build();

                    HtmlRenderer renderer = HtmlRenderer.builder(options).build();

                    Document document = parser.parse(oneById.getContent());

                    htmlContent = renderer.render(document);

                    // 非法操作，数据库的东西除非需要改才能改
                    // 不能用来存值
//            _article.setContent(html);
                } else {
                    htmlContent = oneById.getContent();
                }

                String articleStr = gson.toJson(oneById);
                Article article = gson.fromJson(articleStr, Article.class);
                article.setContent(htmlContent);
                // change cache
                // 添加缓存article cache
                redisUtil.set(constant.redis_article_cache_key + _id, gson.toJson(article), LRConstants.TimeValue.min_s * 5);

                return ResponseResult.success().setData(article);

            // 管理员可以看的文章
            case LRConstants.db_ban_status:
            case IArticleService.constant.db_draft_state_article:
                // complete data
                User userIsLogin = userService.checkUserIsLogin();

                if (userIsLogin == null) {
                    return ResponseResult.failed(ResponseState.user_not_login_message, ResponseState.user_not_login_code);
                }

                String roles = userIsLogin.getRoles();

                if (roles.equals(IUserService.constant.DB_ROLE_ADMIN)) {
                    return ResponseResult.success().setData(oneById);
                } else {
                    return ResponseResult.failed(ResponseState.no_power_message, ResponseState.no_power_code);
                }

            default:
                return ResponseResult.failed(ResponseState.not_support_operation_message, ResponseState.not_support_operation_code);
        }
    }

    /**
     * 更新文章
     *
     * @param _id      文章id
     * @param _article 文章对象
     * @return 自定义返回对象
     */
    @Override
    public ResponseResult update(String _id, Article _article) {
        // check data
        Article oneById = articleDao.findOneById(_id);

        if (oneById == null) {
            return ResponseResult.failed(ResponseState.data_not_have_message, ResponseState.data_not_have_code);
        }

        // complete data
        if (!TextUtil.isEmpty(_article.getTitle())) {
            oneById.setTitle(_article.getTitle());
        }

        if (!TextUtil.isEmpty(_article.getSummary())) {
            oneById.setSummary(_article.getSummary());
        }

        if (!TextUtil.isEmpty(_article.getContent())) {
            oneById.setContent(_article.getContent());
        }

        if (!TextUtil.isEmpty(_article.getLabel())) {
            oneById.setLabel(_article.getLabel());
        }

        if (!TextUtil.isEmpty(_article.getCategoryId())) {
            oneById.setCategoryId(_article.getCategoryId());
        }

        if (!TextUtil.isEmpty(_article.getCover())) {
            oneById.setCover(_article.getCover());
        }

        if (!TextUtil.isEmpty(_article.getState())) {
            oneById.setState(_article.getState());
        }

        oneById.setUpdateTime(new Date());

        // save data
        articleDao.save(oneById);

        // change cache
        redisUtil.del(constant.redis_article_cache_key + _id);
        redisUtil.del(constant.redis_list_article_first_page_cache);

        return ResponseResult.success();
    }

    /**
     * 删除文章
     *
     * @param _id id
     * @return 自定义返回对象
     */
    @Override
    public ResponseResult delete(String _id) {
        // delete  data
        // 先删除评论 因为评论有外键 是文章的id
        // 唇亡齿寒
        commentDao.deleteAllByArticleId(_id);

        // delete main sql data
        int i = articleDao.deleteAllById(_id);

        if (i == 0) {
            return ResponseResult.failed(ResponseState.data_not_have_message, ResponseState.data_not_have_code);
        }

        // delete cache data
        redisUtil.del(constant.redis_article_cache_key + _id);
        redisUtil.del(constant.redis_article_view_count_cache_key + _id);
        redisUtil.del(constant.redis_list_article_first_page_cache);
        solrService.deleteArticleById(_id);

        return ResponseResult.success();
    }

    /**
     * 标记删除
     *
     * @param _id 文章id
     * @return 自定义返回对象
     */
    @Override
    public ResponseResult deleteArticleByState(String _id) {
        // delete data
        int i = articleDao.updateState(_id, LRConstants.db_ban_status);

        if (i == 0) {
            return ResponseResult.failed(ResponseState.data_not_have_message, ResponseState.data_not_have_code);
        }

        redisUtil.del(constant.redis_article_cache_key + _id);
        redisUtil.del(constant.redis_article_view_count_cache_key + _id);
        redisUtil.del(constant.redis_list_article_first_page_cache);
        solrService.deleteArticleById(_id);
        return ResponseResult.success();
    }
}
